#include <algorithm>
#include <list>

#include "simulator_sim.h"
#include "icnt_wrapper.h"

#include "option_parser.h"
#include "pe.h"
#include "l2cache.h"

#define CORE 0x01
#define L2 0x02
#define DRAM 0x04
#define ICNT 0x08

SimulatorConfig::SimulatorConfig()
{
    m_valid = false;
    m_pe_config = new PEConfig();
    m_memory_config = new MemoryConfig();

    m_debug_timing=false;
}

void SimulatorConfig::init(option_parser_t opp)
{
    reg_options(opp);
    option_parser_cmdline(opp, sg_argc, sg_argv);

    m_pe_config->init();
    m_memory_config->init();

    init_clock_domains();

    m_valid = true;
}

SIMULATOR::SIMULATOR(SimulatorConfig *simulator_config) : m_ramulator_wrapper(simulator_config->simulator_ramulator_config, simulator_config->num_pes, simulator_config->simulator_ramulator_cache_line_size)
{
    m_pes = nullptr;

    m_config = simulator_config;
    m_pe_config = simulator_config->m_pe_config;
    m_memory_config = simulator_config->m_memory_config;

    core_time = 0.0f;
    icnt_time = 0.0f;
    l2_time = 0.0f;
    dram_time = 0.0f;

    simulator_sim_cycle = 0;
    simulator_tot_sim_cycle = 0;

    m_pes = new PE *[m_config->num_pes];
    for (uint32_t i = 0; i < m_config->num_pes; i++)
        m_pes[i] = new PE(i, this, m_config);

    m_memory_partition_unit = new MemoryPartitionUnit *[m_memory_config->m_n_mem];
    m_memory_sub_partition = new MemorySubPartition *[m_memory_config->m_n_mem_sub_partition];
    for (uint32_t i = 0; i < m_memory_config->m_n_mem; i++)
    {
        m_memory_partition_unit[i] = new MemoryPartitionUnit(i, m_memory_config, this, &m_ramulator_wrapper);
        for (uint32_t p = 0; p < m_memory_config->m_n_sub_partition_per_memory_channel; p++)
        {
            uint32_t submpid = i * m_memory_config->m_n_sub_partition_per_memory_channel + p;
            m_memory_sub_partition[submpid] = m_memory_partition_unit[i]->get_sub_partition(p);
        }
    }

    icnt_wrapper_init();
    icnt_create(m_config->num_pes, m_memory_config->m_n_mem_sub_partition);

    fprintf(stdout, "SIMULATOR-Sim uArch: performance model initialization complete.\n");
}
void SIMULATOR::init()
{
}
bool SIMULATOR::active()
{
    bool pes_done = true, mem_done=true;
    for (uint32_t i = 0; i < m_config->num_pes; i++)
        pes_done &= !m_pes[i]->active();
    for (uint32_t i=0; i< m_config->m_memory_config->m_n_mem; i++)
        mem_done &= !m_memory_partition_unit[i]->busy();
    //if (done)
      //  m_ramulator_wrapper.finish();

    return !pes_done || !mem_done || ::icnt_busy();
}
uint32_t SIMULATOR::next_clock_domain()
{
    double smallest = std::min(std::min(core_time, icnt_time), dram_time);
    uint32_t mask = 0x00;
    if (l2_time <= smallest)
    {
        smallest = l2_time;
        mask |= L2;
        l2_time += m_config->l2_period;
    }
    if (icnt_time <= smallest)
    {
        mask |= ICNT;
        icnt_time += m_config->icnt_period;
    }
    if (dram_time <= smallest)
    {
        mask |= DRAM;
        dram_time += m_config->dram_period;
    }
    if (core_time <= smallest)
    {
        mask |= CORE;
        core_time += m_config->core_period;
    }
    return mask;
}

void SIMULATOR::cycle()
{
    uint32_t clock_mask = next_clock_domain();
    if (clock_mask & CORE)
    {
        for (uint32_t i = 0; i < m_config->num_pes; i++)
            m_pes[i]->icnt_cycle();
    }
    // L2->icnt queue pop and push into icnt
    if (clock_mask & ICNT)
    {
        for (uint32_t i = 0; i < m_memory_config->m_n_mem_sub_partition; i++)
        {
            MemFetch *mf = m_memory_sub_partition[i]->top();
            if (mf)
            {
                unsigned response_size = mf->get_is_write() ? mf->get_ctrl_size() : mf->size();
                if (::icnt_has_buffer(m_config->mem2device(i), response_size))
                {
                    // if (!mf->get_is_write())
                    mf->set_return_timestamp(simulator_sim_cycle + simulator_tot_sim_cycle);
                    mf->set_status(IN_ICNT_TO_SHADER, simulator_sim_cycle + simulator_tot_sim_cycle);
                    ::icnt_push(m_config->mem2device(i), mf->get_request_id(), mf, response_size);
                    m_memory_sub_partition[i]->pop();
                    // partiton_replys_in_parallel_per_cycle++;
                }
                else
                {
                    // gpu_stall_icnt2sh++;
                }
            }
            else
            {
                m_memory_sub_partition[i]->pop();
            }
        }
    }
    if (clock_mask & DRAM)
    {
        m_ramulator_wrapper.tick();
        for (unsigned i = 0; i < m_memory_config->m_n_mem; i++)
        {
            m_memory_partition_unit[i]->dram_cycle(); // Issue the dram command (scheduler + delay model)
        }
    }

    if (clock_mask & L2)
    {
        // m_power_stats->pwr_mem_stat->l2_cache_stats[CURRENT_STAT_IDX].clear();
        for (uint32_t i = 0; i < m_memory_config->m_n_mem_sub_partition; i++)
        {
            // move memory request from interconnect into memory partition (if not
            // backed up) Note:This needs to be called in DRAM clock domain if there
            // is no L2 cache in the system In the worst case, we may need to push
            // SECTOR_CHUNCK_SIZE requests, so ensure you have enough buffer for them
            if (m_memory_sub_partition[i]->full(SECTOR_CHUNCK_SIZE))
            {
                // gpu_stall_dramfull++;
            }
            else
            {
                MemFetch *mf = (MemFetch *)icnt_pop(m_config->mem2device(i));
                m_memory_sub_partition[i]->push(mf, simulator_sim_cycle + simulator_tot_sim_cycle);
                // if (mf) partiton_reqs_in_parallel_per_cycle++;
            }
            m_memory_sub_partition[i]->cache_cycle(simulator_sim_cycle + simulator_tot_sim_cycle);
            // m_memory_sub_partition[i]->accumulate_L2cache_stats(
            // m_power_stats->pwr_mem_stat->l2_cache_stats[CURRENT_STAT_IDX]);
        }
    }

    if (clock_mask & ICNT)
    {
        icnt_transfer();
    }

    if (clock_mask & CORE)
    {
        for (uint32_t i = 0; i < m_config->num_pes; i++)
            m_pes[i]->cycle();
    }
}

void SimulatorConfig::reg_options(option_parser_t opp)
{
    m_pe_config->reg_options(opp);
    m_memory_config->reg_options(opp);

    option_parser_register(opp, "-debug_timing", OPT_BOOL,
                           &m_debug_timing, "enable debug timing",
                           "0");

    option_parser_register(opp, "-simulator_clock_domains", OPT_CSTR,
                           &simulator_clock_domains,
                           "Clock Domain Frequencies in MhZ {<Core Clock>:<ICNT "
                           "Clock>:<L2 Clock><DRAM Clock>}",
                           "500.0:2000.0:2000.0:2000.0");

    option_parser_register(opp, "-simulator_n_pes", OPT_UINT32,
                           &num_pes,
                           "number of simd cores per cluster", "16");

    option_parser_register(opp, "-simulator_ramulator_config", OPT_CSTR, &simulator_ramulator_config, " Ramulator config file address.", "/home/zl/Documents/simulator-sim/Ramulator_configs");
    option_parser_register(opp, "-simulator_ramulator_cache_line_size", OPT_INT32, &simulator_ramulator_cache_line_size, " Ramulator cache line size.", "64");
}

void SimulatorConfig::init_clock_domains()
{
    sscanf(simulator_clock_domains, "%lf:%lf:%lf:%lf", &core_freq, &icnt_freq, &l2_freq, &dram_freq);
    core_freq = core_freq MhZ;
    icnt_freq = icnt_freq MhZ;
    l2_freq = l2_freq MhZ;
    dram_freq = dram_freq MhZ;
    core_period = 1 / core_freq;
    icnt_period = 1 / icnt_freq;
    l2_period = 1 / l2_freq;
    dram_period = 1 / dram_freq;
    printf("SIMULATOR uArch: clock freqs: %lf:%lf:%lf\n", core_freq,
           icnt_freq, dram_freq);
    printf("SIMULATOR uArch: clock periods: %.20lf:%.20lf:%.20lf\n",
           core_period, icnt_period, dram_period);
}

void MemoryConfig::reg_options(class OptionParser *opp)
{
    option_parser_register(opp, "-simulator_perf_sim_memcpy", OPT_BOOL,
                           &m_perf_sim_memcpy, "Fill the L2 cache on memcpy",
                           "1");
    option_parser_register(opp, "-simulator_simple_dram_model", OPT_BOOL,
                           &simple_dram_model,
                           "simple_dram_model with fixed latency and BW", "0");
    option_parser_register(opp, "-simulator_dram_scheduler", OPT_INT32,
                           &scheduler_type, "0 = fifo, 1 = FR-FCFS (defaul)",
                           "1");
    option_parser_register(opp, "-simulator_dram_partition_queues", OPT_CSTR,
                           &simulator_L2_queue_config, "i2$:$2d:d2$:$2i", "8:8:8:8");

    option_parser_register(opp, "-l2_ideal", OPT_BOOL, &l2_ideal,
                           "Use a ideal L2 cache that always hit", "0");
    option_parser_register(opp, "-simulator_cache:dl2", OPT_CSTR,
                           &m_l2_config.m_config_string,
                           "unified banked L2 data cache config "
                           " {<nsets>:<bsize>:<assoc>,<rep>:<wr>:<alloc>:<wr_"
                           "alloc>,<mshr>:<N>:<merge>,<mq>}",
                           "64:128:8,L:B:m:N,A:16:4,4");
    option_parser_register(
        opp, "-simulator_n_mem", OPT_UINT32, &m_n_mem,
        "number of memory modules (e.g. memory controllers) in gpu", "8");
    option_parser_register(opp, "-simulator_n_sub_partition_per_mchannel", OPT_UINT32,
                           &m_n_sub_partition_per_memory_channel,
                           "number of memory subpartition in each memory module",
                           "1");
    option_parser_register(opp, "-simulator_n_mem_per_ctrlr", OPT_UINT32,
                           &simulator_n_mem_per_ctrlr,
                           "number of memory chips per memory controller", "1");
    option_parser_register(opp, "-simulator_memlatency_stat", OPT_INT32,
                           &simulator_memlatency_stat,
                           "track and display latency statistics 0x2 enables MC, "
                           "0x4 enables queue logs",
                           "0");
    option_parser_register(opp, "-simulator_frfcfs_dram_sched_queue_size", OPT_INT32,
                           &simulator_frfcfs_dram_sched_queue_size,
                           "0 = unlimited (default); # entries per chip", "0");
    option_parser_register(opp, "-simulator_dram_return_queue_size", OPT_INT32,
                           &simulator_dram_return_queue_size,
                           "0 = unlimited (default); # entries per chip", "0");
    option_parser_register(opp, "-simulator_dram_buswidth", OPT_UINT32, &busW,
                           "default = 4 bytes (8 bytes per cycle at DDR)", "4");
    option_parser_register(
        opp, "-simulator_dram_burst_length", OPT_UINT32, &BL,
        "Burst length of each DRAM request (default = 4 data bus cycle)", "4");
    option_parser_register(opp, "-dram_data_command_freq_ratio", OPT_UINT32,
                           &data_command_freq_ratio,
                           "Frequency ratio between DRAM data bus and command "
                           "bus (default = 2 times, i.e. DDR)",
                           "2");
    option_parser_register(
        opp, "-simulator_dram_timing_opt", OPT_CSTR, &simulator_dram_timing_opt,
        "DRAM timing parameters = "
        "{nbk:tCCD:tRRD:tRCD:tRAS:tRP:tRC:CL:WL:tCDLR:tWR:nbkgrp:tCCDL:tRTPL}",
        "4:2:8:12:21:13:34:9:4:5:13:1:0:0");

    option_parser_register(opp, "-dram_latency", OPT_UINT32, &dram_latency,
                           "DRAM latency (default 30)", "30");
    option_parser_register(opp, "-dram_dual_bus_interface", OPT_UINT32,
                           &dual_bus_interface,
                           "dual_bus_interface (default = 0) ", "0");
    option_parser_register(opp, "-dram_bnk_indexing_policy", OPT_UINT32,
                           &dram_bnk_indexing_policy,
                           "dram_bnk_indexing_policy (0 = normal indexing, 1 = "
                           "Xoring with the higher bits) (Default = 0)",
                           "0");
    option_parser_register(opp, "-dram_bnkgrp_indexing_policy", OPT_UINT32,
                           &dram_bnkgrp_indexing_policy,
                           "dram_bnkgrp_indexing_policy (0 = take higher bits, 1 "
                           "= take lower bits) (Default = 0)",
                           "0");
    option_parser_register(opp, "-dram_seperate_write_queue_enable", OPT_BOOL,
                           &seperate_write_queue_enabled,
                           "Seperate_Write_Queue_Enable", "0");
    option_parser_register(opp, "-dram_write_queue_size", OPT_CSTR,
                           &write_queue_size_opt, "Write_Queue_Size", "32:28:16");
    option_parser_register(
        opp, "-dram_elimnate_rw_turnaround", OPT_BOOL, &elimnate_rw_turnaround,
        "elimnate_rw_turnaround i.e set tWTR and tRTW = 0", "0");
    option_parser_register(opp, "-icnt_flit_size", OPT_UINT32, &icnt_flit_size,
                           "icnt_flit_size", "32");
    m_address_mapping.addrdec_setoption(opp);
}
